owasp移动安全测试之Android平台综述
这一章节从系统架构方面介绍Android平台,将讨论以下四个部分:
Android安全体系
Android应用程序结构
进程间通信(Inter-process Communication,IPC)
Android 应用程序发布
关于更多Android平台的信息,请查阅Android开发者网站。
Android安全体系
Android是谷歌基于Linux开发的一个开源的移动操作系统。现在,该平台已成为众多现代设备的操作系统,像手机,平板,可穿戴设备,TV,和其他智能设备。通常Android会预安装一些应用程序,但也支持从Google Play Store和其他应用市场下载应用程序并安装。
Android的软件栈由如下几层组成,每层都定义了接口,并提供特定的功能。
最底层是Linux内核。内核之上是硬件抽象层(Hardware Abstraction Layer,HAL),它定义了与内置硬件组件进行交互的标准接口。对HAL的实现被打包进共享库模块,在Android系统需要时调用。这是应用程序与设备硬件交互的接口——例如,某一预置程序可以通过它来使用设备的麦克风和扬声器。
Android app通常由Java编写并编译成dalvik字节码,dalvik字节码与传统的Java 字节码有些许不同。dalvik字节码的产生过程是:Java代码转换成.class文件,然后用dx
将 JVM 字节码转换成Dalvik的.dex格式。
目前版本的Android在ART (Android Runtime)上执行这种字节码。ART是DVM的继承者。Dalvik和ART的主要不同之处在于字节码的运行方式。
在Dalvik中,字节码是在运行时被转化为机器码的,这种编译方式叫做即时编译(just-in-time,JIT)。JIT编译会影响性能:app的每一次运行都需要编译。为提升性能,ART引入了ahead-of-time (AOT)编译。如它的名字所暗示的那样,app在它运行前就预编译好了。这些预编译的机器码会在后面的运行中用到。AOT通过减少资源消耗提高了性能。
Android app并不会直接访问硬件资源,并且每一个app都运行在自己的沙箱中。这样就能够精确控制资源和app:例如,某一崩溃的app不会影响到设备上运行的其他app。同时,ART控制分配给app的最大资源数,以防任一app占用太多资源。
Android用户和用户组
尽管Android操作系统是基于Linux的,但它和其他的类Unix系统对用户账号的实现方式并不一样。在Android中,支持linux内核的多用户:除开少量例外,每个app都以一个linux用户的身份运行,有效的与其他app和其他的操作系统隔离开。
system/core/include/private/android_filesystem_config.h 文件中列出了分配给系统进程的预定义的用户和用户组。 后面安装其他app的时候,它们的UID(UserID,用户id)就会被加进来。更多信息,请查看Bin Chen关于Android沙箱的博文。
例如,Android Nougat定义了以下系统用户:
...
...
Android应用程序结构
与操作系统的通信
Android app利用Android Framework与系统服务进行交互。Android Framework是一个提供了高级Java API的抽象层。大部分系统服务的使用都是通过正常的Java 函数调用并转化为IPC调用。下面是一些系统服务的例子:
- Connectivity (Wi-Fi, Bluetooth, NFC, etc.)
- Giles
- Cameras
- Geolocation (GPS)
- Microphone
framework还提供常见的安全功能,例如加密。
这些API随着每一次新的Android系统发布一起改变。高危漏洞修复和安全补丁通常也适用于先前的版本。在撰写本文时支持的最早的Android版本是4.4(KitKat),API level 19,现在的版本是Android 7.1 (Nougat), API level 25.
值得注意的API版本是:
Android 4.2 Jelly Bean (API 16) in November 2012 (引入了SELinux)
Android 4.3 Jelly Bean (API 18) in July 2013 (默认开启SELinux)
Android 4.4 KitKat (API 19) in October 2013 (引入了几个新的API 和ART)
Android 5.0 Lollipop (API 21) in November 2014 (默认使用ART, 还增加了许多新的功能)
Android 6.0 Marshmallow (API 23) in October 2015 (有很多新的功能和改进, 包括授权、在安装时给出申请的权限的细节,用户不再是对权限一无所知)
Android 7.0 Nougat (API 24-25) in August 2016 (ART中新的JIT)
Android 8.0 O (API 26) beta (修补了很多安全问题)
App 文件夹结构
安装好的Android app位于/data/app/[package-name]
.例如,YouTube app就位于:
/data/app/com.google.android.youtube-1/base.apk
APK文件是一个压缩文件,里面有代码和运行这个app所需要的资源。此文件与开发人员创建的那个签过名的应用程序包相同。它实际上是一个ZIP压缩包,目录结构如下:
unzip base.apk
ls -lah
-rw-r--r-- 1 sven staff 11K Dec 5 14:45 AndroidManifest.xml
drwxr-xr-x 5 sven staff 170B Dec 5 16:18 META-INF
drwxr-xr-x 6 sven staff 204B Dec 5 16:17 assets
-rw-r--r-- 1 sven staff 3.5M Dec 5 14:41 classes.dex
drwxr-xr-x 3 sven staff 102B Dec 5 16:18 lib
drwxr-xr-x 27 sven staff 918B Dec 5 16:17 res
-rw-r--r-- 1 sven staff 241K Dec 5 14:45 resources.arsc
AndroidManifest.xml:包括对app的包名的定义,target和min API 版本,app配置,组件,用户授予权限,等等。
META-INF:包含了app的元数据。
MANIFEST.MF:保存app资源的hash。
CERT.RSA:app的证书
CERT.SF:资源列表以及MANIFEST.MF的SHA-1.
asset:这个目录中主要存放一些随程序打包的文件(在app中使用到的文件,例如XML文件,JS文件,还有图片),这些文件均可用AssetManager检索到。
classes.dex:classes被编译成DEX文件的格式,这样DVM/ART方可运行。dex是在DVM下的java字节码。它针对小型设备进行了优化。
lib:这个目录存放apk用到的第三方jar包。
res:这个目录存放尚未编译进resource.arsc的资源
resource.arsc:包含预编译资源的文件,例如布局文件。
直接用一般的unzip工具来解压apk文件还会看到一些阅读起来有点困难的文件。AndroidManifest.xml
被编码成二进制的xml格式,文本阅读器无法解读。还有,app的资源还被打包成一个压缩文件。解压Android 应用程序包的一个最好的方式就是用 apktool 。在使用默认的命令行标志运行apktool时,它会将Manifest 文件解码为基于文本的xml格式,并提取文件资源(它还会反编译.dex文件,将其转化为smali代码——稍后我们会与它再次见面)。
$ apktool d base.apk
I: Using Apktool 2.1.0 on base.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /Users/sven/Library/apktool/framework/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
$ cd base
$ ls -alh
total 32
drwxr-xr-x 9 sven staff 306B Dec 5 16:29 .
drwxr-xr-x 5 sven staff 170B Dec 5 16:29 ..
-rw-r--r-- 1 sven staff 10K Dec 5 16:29 AndroidManifest.xml
-rw-r--r-- 1 sven staff 401B Dec 5 16:29 apktool.yml
drwxr-xr-x 6 sven staff 204B Dec 5 16:29 assets
drwxr-xr-x 3 sven staff 102B Dec 5 16:29 lib
drwxr-xr-x 4 sven staff 136B Dec 5 16:29 original
drwxr-xr-x 131 sven staff 4.3K Dec 5 16:29 res
drwxr-xr-x 9 sven staff 306B Dec 5 16:29 smali
AndroidManifest.xml:解码的Manifest文件,可以用文本编辑器打开和编写。
apktool.yml:该文件中包含了apktool输出文件的信息。
original:这个文件夹中包含了MANIFEST.MF文件,而MANIFEST.MF文件中有jar包中文件的信息。
res:存放app的资源文件。
smali:这个目录存放的是.dex对应的smali文件。每一个app都有一个data文件夹,用以存储程序运行时产生的数据。这个文件夹位于
/data/data/[package-n
ame]
,其结构如下所示:
drwxrwx--x u0_a65 u0_a65 2016-01-06 03:26 cache
drwx------ u0_a65 u0_a65 2016-01-06 03:26 code_cache
drwxrwx--x u0_a65 u0_a65 2016-01-06 03:31 databases
drwxrwx--x u0_a65 u0_a65 2016-01-10 09:44 files
drwxr-xr-x system system 2016-01-06 03:26 lib
drwxrwx--x u0_a65 u0_a65 2016-01-10 09:44 shared_prefs
cache:这个目录用于数据缓存。例如,WebView的cache就存放在这里。
code_cache:这个目录是文件系统专为应用程序设计的,用于存放缓存的代码。Android Lollipop及其之后的Android系统,在app或整个platform升级之后,系统会删除这个目录下的所有文件。
database:这个文件夹存放app在运行时产生的SQLite数据库文件。例如,用户数据文件。
files:存放app产生的常规文件。
lib:存放用C/C++写的原生库。这些库可以是多种形式,包括.so 和.dll(x86支持)。这个文件下的子文件夹是这个app所持有的原生库支持的平台,包括:
armeabi:适用于所有的ARM处理器
armeabi-v7a:仅适用于7及其以上的ARM处理器
arm64-v8a:仅适用于8及其以上的64位ARM处理器
x86:仅适用于x86处理器
x86_64:仅适用于x86_64处理器
mips:适用于MIPS处理器
shared_prefs:这个文件夹存放一个xml文件,xml文件的内容通过SharedPreferences APIs 保存。
标准应用程序的UID/GID
Android用linux的用户管理机制来隔离app。这个方法跟linux下的用户管理机制不同,在linux中,一个用户通常可以运行多个app。Android为每一个app分配一个唯一的UID,并运行在不同的进程上。这样一来,每个app都只能访问自己的资源。这种保护机制由Linux内核强制执行。
通常,app的UID的取值范围位10000到99999。Android app的用户名就来自这个uid。例如,如果某个app的uid是10188,那么它的用户名就是u0_a188
. 在授予app申请的权限时,相应的gid也会添加到该app进程中。例如,某个app的uid是10188,它的gid是3003(inet)。该组可以使用android.permission.INTERNET 权限。对其使用id
命令,输出结果如下:
id
uid=10188(u0_a188) gid=10188(u0_a188) groups=10188(u0_a188),3003(inet),9997(everybody),50188(all_a188) context=u:r:untrusted_app:s0:c512,c768
gid和权限的对应关系在frameworks/base/data/etc/platform.xml 中定义:
<permission name="android.permission.INTERNET" >
<group gid="inet" />
</permission>
<permission name="android.permission.READ_LOGS" >
<group gid="log" />
</permission>
<permission name="android.permission.WRITE_MEDIA_STORAGE" >
<group gid="media_rw" />
<group gid="sdcard_rw" />
</permission>
app 沙箱
app在Android应用程序沙箱中运行,沙箱将app的数据和运行代码与设备上的其他app隔离开来。这种隔离又增添了一层安全。
安装新的app会创建一个以该app包名命名的目录——/data/data/[package-name]
.这个目录下是该app的数据。同时设置它的Linux目录权限,这样该目录方可被app的唯一uid读写。
我们可以通过查看/data/data
文件夹的文件系统权限来确认这件事。例如,我们可以看到Google Chrome和Calendar各在一个文件夹内,并以不同的用户账号运行:
drwx------ 4 u0_a97 u0_a97 4096 2017-01-18 14:27 com.android.calendar
drwx------ 6 u0_a120 u0_a120 4096 2017-01-19 12:54 com.android.chrome
希望他们的应用共享xi一个普通沙箱的开发人员可以避开沙箱。当两个app的签名相同并且显示共享同一个uid(在他们的AndroidManifest.xml文件中又sharedUserId)时,它们可以访问对方的数据文件夹。参看下面的NFC app的例子:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.nfc"
android:sharedUserId="android.uid.nfc">
zygote
Zygote
进程在Android初始化时创建。Zygote是用来启动app的系统服务。Zygote进程是一个“基础的”进程,它拥有app需要的所有核心库。在启动之后,Zygote会打开/dev/socket/zygote
socket ,并监听所有来自本地客户端的连接。当它接收到一个连接后,它会fork一个新的进程,之后这个进程将加载并运行特定的app代码。
app 生命周期
在Android中,app的生命周期由操作系统控制。当一个app组件启动时会创建一个新的linux进程,并且该app内不会有其他组件运行。Android操作系统会在后面不再需要这个app时或者需要把内存分配给更重要的app时kill掉这个进程。是否杀掉一个进程取决于用户与该进程的交互状态。通常,一个进程会是以下四种状态之一:
前台进程(例如,一个运行在屏幕上的activity,或者一个正在运行的广播接收器)。
可见进程是用户可察觉的进程,所以kill掉它会给用户体验带来负面影响。可见进程的一个例子是,一个正在运行的activity,它可以让用户在屏幕上看到它,但是它又不是前台进程。
服务进程是运行已使用 startService() 方法启动的服务的进程。尽管这些进程对用户并不直接可见,但是它们是用户在意的(例如后台网络数据上传下载),所以操作系统会一直运行这些进程,直到内存不足以维持所有的前台和可见进程。
缓存进程是目前不需要的进程,所以操作系统在需要内存的时候可随意杀掉它们。app需要实现回调函数,例如,在app进程第一次创建时会调用
onCreate
句柄。其他的回调函数包括:onLowMemory
、onTrimMemory
和onConfigurationchanged
。
manifest
每个app都有一个manifest文件,它以二进制xml格式保存内容。这个manifest文件的标准文件名是:AndroidManifest.xml。它位于apk文件的根目录下。
manifest文件描述了app的结构,它的组件(activity 活动、service服务、content provider 内容提供器、intent receivers 意图接收器),还有它所申请的权限。它还包括其他的app一般的元数据,例如app的图标,版本号,和主题。这个文件还会列出其他信息,例如兼容的API(最小,目标和最大的SDK版本)还有它应该安装在哪一种存储环境上(外存或内存)。
这里有一个manifest文件的例子,包含了包名(通常是一个方向的URL,但也可以是任意的字符串)。它还列出了app的版本,相关的SDK,申请的权限,暴露的内容提供器,和意图过滤器一起使用的广播接收器,还有对app的描述以及它的activity。
<manifest
package="com.owasp.myapplication"
android:versionCode="0.1" >
<uses-sdk android:minSdkVersion="12"
android:targetSdkVersion="22"
android:maxSdkVersion="25" />
<uses-permission android:name="android.permission.INTERNET" />
<provider
android:name="com.owasp.myapplication.myProvider"
android:exported="false" />
<receiver android:name=".myReceiver" >
<intent-filter>
<action android:name="com.owasp.myapplication.myaction" />
</intent-filter>
</receiver>
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.Material.Light" >
<activity
android:name="com.owasp.myapplication.MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
</application>
</manifest>
manifest文件中的选项的完整列表可查阅Android官方manifest文档。
app 组件
Android app由几类高级组件组成,主要的有:
Activity 活动
Fragment 碎片
Intent 意图
Broadcast receiver 广播接收器
Content provider 内容提供器
所有这些元素都由Android操作系统以定义好的类提供,可通过API使用它们。
Activity
activity构成了所有app的可见部分。每屏一个activity,所以,如果一个app有三个不同屏幕的内容,那么它应该实现了三个不同的activity。所有的activity都继承于Activity类。它们包含了所有的用户交互元素:fragment碎片,view视图,和layout布局。
每个activity都需要以如下格式在manifest文件中声明:
<activity android:name="ActivityName">
</activity>
未在manifest中声明的activity将无法显示,而且试图启动它们会引发异常。
和app一样,activity也有它们之间的生命周期,需要监视系统变化去处理它们。activity有以下状态:active,paused,stopped,和inactive。这些状态由Android操作系统控制。通常,activity由以下几种事件管理器:
onCreate
onSaveInstanceState
onStart
onResume
onRestoreInstanceState
onPause
onStop
onRestart
onDestroy
一个app并不需要显式地实现所有的事件管理器,如果不修改它们,它们会执行默认action。通常,app开发者要重载onCreate
函数。在onCreate中写了大多数用户交互组件的声明和初始化。如果资源(像网络连接和数据库连接)需要显示的释放或需要在app关闭时执行特定操作,那么就要重载onDestroy
。
fragment 碎片
fragment代表了一个行为或是activity中用户交互的一部分。fragment是在Android Honeycomb 3.0(API 11)中引入的。
fragment旨在封装部分接口以实现功能重用和适应不同的屏幕大小。fragment是完全独立的实体,可以在其中加入它所需要的任何组件(它们有自己的layout,button等等)。但是要使用它们需得把它嵌入activity中:fragment不单独存在。它们有自己的生命周期,与实现它们的activity的生命周期绑在一起。
因为fragment有它们自己的生命周期,所以Fragment类中包含的事件管理器可以重定义和重写。这些事件管理器包括:onAttach
、onCreate
、onStart
、onDestroy
和onDetach
。还有其他的,读者可以查看Android Fragment 详解进行了解。
Fragment可以简单地通过继承Android提供的Fragment类来实现:
public class myFragment extends Fragment {
...
}
Fragment不需要在manifest文件中声明,因为它依赖于activity。
activity可以使用Fragment 管理器(FragmentManager类)管理自身的fragment。有了这个类,activity可以很容易地找到,添加,移除和替换相关fragment。
fragment管理器可以使用如下方法创建:
FragmentManager fm = getFragmentManager();
fragment不需要有用户接口。使用它们可以很方便有效的管理与用户交互有关的后台操作。fragment可以永久声明,所以操作系统可以保留它的状态,即使它的activity已经被销毁了。
进程间通信IPC
正如前面所说,每一个Android进程都有其自身的沙箱地址空间。IPC设备确保app可以安全的交换信号和数据。Android的IPC是基于Binder的(它是在OpenBinder
上的定制实现),并非依赖于默认的Linux IPC 设备。大多数Android系统服务和高级的IPC服务都依赖于于Binder。
Binder一词代表很多含义,包括:
Binder 驱动:内核驱动
Binder 协议:用于与binder驱动通信的低级的基于ioctl的协议
IBinder 接口:一个定义好的,用Binder对象实现的行为
Binder对象:对IBinder接口的实现
Binder 服务:对Binder对象的实现;例如,位置服务,和传感器服务
Binder 客户:使用到Binder服务的对象
Binder框架包含了一个客户端-服务器通信模型。要使用IPC,app需在代理对象中调用IPC函数。代理对象透明的将参数打包并向Binder服务器发送一个transaction,Binder服务器以字符驱动的形式实现(/dev/binder)。服务器拥有一个进程池用来处理接收到的请求和发送信息到目标对象。从客户端app来看,一切都好像只是一次常规的函数调用——所有的活都已经由Binder框架做完了。
Binder概述。图片来源:Android Binder by Thorsten Schreiber
允许其他应用程序绑定它们的服务称为bind service。这些服务需给客户端提供一个IBinder接口。开发者用AIDL(Android Interface Descriptor Language,Android接口定义语言)来为远程服务定义接口。
Servicemanger是一个用来管理系统服务注册和查找的系统守护进程。它包含了所有已注册服务的 name/Binder对。用android.os.ServiceManager
中的addService
添加服务,以name为参数,用getService
检索服务:
public static IBinder getService(String name)
你可以用service list
命令查看系统服务:
$ adb shell service list
Found 99 services:
0 carrier_config: [com.android.internal.telephony.ICarrierConfigLoader]
1 phone: [com.android.internal.telephony.ITelephony]
2 isms: [com.android.internal.telephony.ISms]
3 iphonesubinfo: [com.android.internal.telephony.IPhoneSubInfo]
Intent
Intent信息是在Binder之上建立的一种异步通信框架。这个框架支持点对点通信和发布-订阅模式的通信。一个Intent就是一个信息对象,可用于请求来自另一个app组件的某一个动作。intent可以以多种方式促进IPC,但最主要的方式是如下三种:
启动activity
一个activity代表了app中的一个屏幕。你可以通过发送一个
startActivity
intent启动一个activity。这个intent需包括该activity和需要的数据。启动服务
服务是在后台完成操作的一个组件,没有用户接口。在Android 5.0(API 21)及以后,你可以用 JobScheduler 启动一个服务。
发送广播
广播就是任何app都可以接收的信息。系统为系统事件发送广播,包括系统启动和要求初始化。你可以用
sendBroadcast
和sendOrderBroadcast
给其他app发送广播。
Intent是用于在app间和组件间发送信息的组件。app可以用它们来给自身的组件发送信息(例如,在app内启动一个activity),也可以给其他app,操作系统发送信息。Intent可用于启动activity和service,执行给定数据的操作,给整个系统广播信息。
有两种类型的 Intent。显式 intent ,显示地给出了要启动的组件的名字(完整的类名)。例如:
Intent intent = new Intent(this, myActivity.myClass);
隐式 Intent ,是发送给操作系统,让它根据给定的数据执行给定操作。
("http://www.example.com" ,在下面的例子中)
由系统来决定哪个app或类来执行相应的服务。例如:
Intent intent = new Intent(Intent.MY_ACTION, Uri.parse("http://www.example.com"));
Intent filer 意图过滤器,是在manifest中的一个表达式,用来指定组件想要接收的intent的类型。例如,通过在activity中声明一个intent filter,其他app就有可能使用特定类型的intent来启动你的activity。否则,你的activity就只能由一个显式的intent来启动了。
Android用intent给app广播信息(例如来电或来信),重要的电池信息(电量不足,比方说),还有网络状态改变(信号不足,例如)。也可以添加 extra 数据 (通过putExtra/getExtra
)
这里是几个由操作系统发送的intent列表。所有的常量都已经在 Intent 类中定义,完整的列表可以查看Android官方文档:
ACTION_CAMERA_BUTTON
ACTION_MEDIA_EJECT
ACTION_NEW_OUTGOING_CALL
ACTION_TIMEZONE_CHANGED
为提高安全性和隐私,使用本地广播管理器在app内发送和接收intent,而不会将它们发送到其他地方。这对于确保敏感和隐私信息(例如,地理位置信息)不流到app之外很有用。
广播接收器
广播接收器是让app接收来自其他app或系统的信息的组件。有了它,app就可以对各种事件(内部的,被其他app初始化,或被操作系统初始化)做出反应。最普遍的用法是更新用户接口,启动服务,更新内容,和创建用户通知。
广播接收器需要在app的manifest文件中声明。manifest要明确指定广播接收器和intent filter之间的关联,以便指明接收器要监听的动作。如果没有声明广播接收器,app不会监听广播信息。但是,也不用运行app去接收intent,当有相关的intent的时候,系统会自动启动该app.
下面是一个在manifest中声明广播接收器的例子:
<receiver android:name=".myReceiver" >
<intent-filter>
<action android:name="com.owasp.myapplication.MY_ACTION" />
</intent-filter>
</receiver>
在接收到一个隐式intent后,Android会列出所有在它们的过滤器中注册了给定action的app。若有多于一个的app注册了相同的action,系统就会让用户从可选列表中选择一个。
广播接收器的一个比较好玩的特点是,它们有优先级。通过这种方式,一个intent可以根据优先级发送给所有满足条件的接收者。
本地广播接收器可以用于确保intent仅在app内部被收到,任何来自其他app的intent都会被丢弃。这对于提高安全性很有用。
内容提供器
Android用 SQLite 永久存储数据:和 Linux 一样,将数据存储在文件中。 SQLite是一款轻量的,高效的,开源的关系型数据存储技术,不需要过多的处理能力,这使得它很适合移动应用 。有操作SQLite的API,里面有很多专门用于处理此类问题的类(Cursor, ContentValues, SQLiteOpenHelper, ContentProvider, ContentResolver, 等等)。
SQLite并不以独立的进程存在,它是app的一部分。属于给定app的数据库默认只能在这个app内访问。但是,内容提供器为提取数据资源(包括数据库和flat file)提供了一种很好的机制,它们也为app间,包括原生app间,的数据共享提供了一种标准的、高效的机制。要访问其他app,内容提供器需要在manifest文件中显式声明该app可以共享它。如若不声明内容提供器,外界将无法访问它们,只能被创建它们的app访问。
内容提供器通过URI寻址策略来实现:它们都使用content://模式。不管资源的类型是什么(SQLite数据库,flat file等等),寻址策略都一样,因此提取资源并提供给开发者一个唯一的方案。内容提供器有所有常规的数据库操作:增、删、改、查。这就意味着,在manifest文件声明的有合法权限的任意app都可以从外面操纵这些数据。
服务
服务是在后台执行任务(数据处理,启动intent,通知,等等),并且没有用户界面的Android OS组件(基于 Service 类)。服务意味着长期执行进程。它们的系统优先级低于所有的活跃app,高于所有的不活跃app。因此,在系统需要资源时,它们不太可能会被杀掉,而且资源再次足够的适合,它们会自动重启。Activity是在主app进程中执行的。它们是执行异步任务的最佳候选人。
权限
因为Android app是安装在沙箱中的,因此初始状态下无法访问用户信息和系统组件(例如,相机和麦克风),Android为系统提供了一个为特定任务预定义的一系列权限,app在需要的时候可以申请。例如,如果你希望你的app能够使用手机相机,你需要申请android.permission.CAMERA
权限。在 Marshmallow(API 23)之前,所有的权限在安装时全部授予。
自 Marshmallow 以后,用户需要在app运行时同意app申请的一些权限。
保护级别
Android权限根据它们的保护级别进行排序,并分为四类:
Normal: 较低的保护水平。让app以最小的风险访问应用程序层的特征,不会给系统、用户或其他系统造成危害。在app安装时授予,并且是默认的保护级别。例如:
android.permission.INTERNET
Dangerous: 这个级别的权限权限让app可以执行有可能有损用户隐私的操作,或者对用户设备的正常操作。这个级别的权限不会在安装的时候授予,用户需决定app能否拥有该权限。例如:
android.permission.RECORD_AUDIO
Signature: 这个级别的权限只在申请这个权限的app和声明了这个权限的app有相同的数字签名的时候才会授予。如果数字签名匹配,将自动授权。例如:
android.permission.ACCESS_MOCK_LOCATION
SystemOrSignature: 这个级别的权限只有在该app已经内嵌到系统镜像或者和声明了该权限的app有相同的数字签名的时候才会授予。例如:
android.permission.ACCESS_DOWNLOAD_MANAGER
申请权限
app可以通过在manifest文件中使用<uses-permission/>
申请 Normal 、Dangerous、Signature级别的权限。下面的例子是在AndroidManifest.xml中申请读取SMS信息的权限:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.permissions.sample" ...>
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<application>...</application>
</manifest>
声明权限
app可以向系统内已安装的其他app暴露自己的特征和内容。为限制对自身组件的访问,它可以使用Android官方定义的任意一个权限,或者自己定义一个。元素中定义了一个新的权限。下面的例子是如何在app中声明一个权限:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.permissions.sample" ...>
<permission
android:name="com.permissions.sample.ACCESS_USER_INFO"
android:protectionLevel="signature" />
<application>...</application>
</manifest>
上面的代码中定义了一个新的权限,叫做:
com.permissions.sample.ACCESS_USER_INFO
, 保护级别是Signature
。任何由这个权限保护的组件都只能被与本app一样的开发者证书签名的app所访问。
为组件指定权限
也可以用权限保护 Android 组件。活动、服务、内容提供器、广播接收器——所有这些都可以用权限机制保护它们的接口。通过在 AndroidManifest.xml 文件中添加 android:permission 志相应的组件标签,可以为活动,服务,和广播接收器增加权限。
<receiver
android:name="com.permissions.sample.AnalyticsReceiver"
android:enabled="true"
android:permission="com.permissions.sample.ACCESS_USER_INFO">
...
</receiver>
内容提供器有一点不同。Android提供了一些通过URI来读、写和访问内容提供器的权限。
android.writePermission
,android.readPermission
:开发者可以分别授予读权限和写权限。android:permission
: 控制对内容提供器的读写的基本权限。android.grantUriPermissoin
: 如果内容提供器可以访问给定的URI就为 true (这个访问暂时绕过了其他权限的限制),否则为 false。
签名与发布过程
一旦一个app成功开发,下一步就是发布并将它分享给其他人。但是app不能简单地添加到应用商店并分享给其他人,因为——它们需要签名。加密签名就好像是开发者放置在app中的一个可验证的标记。它证明了app的开发者身份,并确保自发布后没有被修改过。
签名过程
在开发的过程中,app使用自动生成的证书进行签名。这个证书是不安全的,只能用来调试。大多数应用商店不认可这种证书,因此,必须创建一个更加安全的证书。在一个 app 在 Android 设备上安装之后,包管理器( Package Manager )必须确保它已使用相应apk中包含的证书进行签名。如果证书的公钥和设备上其他任何一个的签名一致,那么这个app就和那个app共享同一个uid。这就从开发者的角度促进了应用程序间的交互。还有,也可以给app授予Signature级别的权限,这样就限制了相同数字签名的app对该应用程序的访问。
APK 签名策略
Android支持两种应用程序签名策略。从Android7.0开始,APK可以用 APK签名策略 v2 (v2策略) 或者 JAR 签名 ( v1策略)。为了向后兼容,以v2签名的apk可以在较老的Android设备上安装,只要前者也用v1签过了。较老的platform会忽略v2签名,只验证v1签名。
JAR签名(v1 策略)
最初对app的签名就和对 标准jar的签名一样,必须在META-INF/MANIFEST.MF
中包含所有的实体。所有的文件都必须使用同一个证书签名。这种签名策略没有保护APK中的某些部分,像是
ZIP元数据。这种策略的缺点就是,apk
验证者在签名之前需要处理不信任的数据结构,而且验证者会丢弃数据结构无法处理的数据。还有,apk验证者需要解压所有的压缩文件,这就要考虑时间和空间。
APK 签名策略(v2 策略)
使用APK 签名策略,整个apk都被hash和签名,还会创建一个 APK 签名块(APK Signing Block)并插入到APK中。在验证的时候,v2策略会检查整个APK文件的签名。这种形式的APK验证会更快,并且对于对抗修改有更多的保护。
创建你的证书
Android使用 公/私 证书对Android app(.apk 文件)进行签名。证书包含一系列信息,就安全而言,密钥是这些信息中最重要的一类了。公证书包含用户公钥,私证书包含用户私钥。公私证书是连在一起的。证书都是唯一的,不能再重新生成。注意,如果证书丢失,就无法恢复,那么将无法更新任何使用这个证书签名的app。
app的开发者要么再次使用keystore中的已经存在的公/私密钥对,要么就再生成一对。在Android
SDK中,新密钥对使用keytool
生成。下面的指令生成一个RSA密钥对,密钥长度为2048位,有效期是7300天=20年。生成的密钥对保存在 'myKeyStore.jks‘ 中,就在当前目录下:
keytool -genkey -alias myDomain -keyalg RSA -keysize 2048 -validity 7300 -keystore myKeyStore.jks -storepass myStrongPassword
要确保安全地储密钥并保证在它的整个生命周期中一直是安全的。任何能够使用该密钥的人都可以更新你的app,而且他更新了什么你根本无法控制(像加入不安全的功能或者访问基于Signature权限的共享内容)。
用户对应用程序及其开发人员的信任完全基于这些证书;因此证书保护和安全管理对于声誉及留住顾客都非常重要,所以密钥绝不能告诉其他人。密钥可以保存在二进制文件中,再使用密码进行加密,一般把这种文件称为“keystore”. keystore的密码一定要足够强,且只有创建者知道。
所以,密钥一般保存在一个专门的机器上,并且开发者访问受限。Android证书的有效期应该比与之关联的app要长(包括该app的更新版本)。例如,Google Play 要求的证书有效期最少得到2033年10月22日。
签名app
整个签名流程要做的就是将app文件(.apk)与开发者的公钥关联起来。要做到这个,开发者要计算apk文件的哈希值,并用密钥对它进行加密。这样第三方就可以验证这个app的可靠性了(比如说,app是否真的来自某个宣称他是开发者的人)。第三方用该开发者的公钥解密apk文件的hash值,看它是否与apk文件的hash真的匹配。
很多IDE都可以对app进行签名,这方便了很多。但是要注意,有些IDE用明文将密钥保存在配置文件中,在这种情况下就要多检查,是否还有人可以访问这个文件,如果必要的话,就把这个信息删掉吧。可以在命令行用 Android SDK(API 24及以上)提供的"apksigner"工具对app进行签名,也可以使用 Java JDK 工具 ”jarsigner“来签名 (API 24之前的版本)。签名的整个过程可以查阅 Android官方文档;下面是用 apksigner 签名的例子:
apksigner sign --out mySignedApp.apk --ks myKeyStore.jks myUnsignedApp.apk
在这个例子中,将使用从开发者的‘myKeyStore.jks’(位于当前目录)中的私钥签名一个未签名的 app ('myUnsignedApp.apk')。签名后的app为 ’mySignedApp.apk‘,可以准备发布到商店中了。
Zipalign
在发布之前,先用zipalign
对其 APK 文件。zipalign
对其apk内的所有未压缩数据(例如,图像,raw file 原始文件,和 4-byte boundary),这样可以在app运行时提高内存管理。zipalign必须在用apksinger
签名之前使用。
发布流程
可以在任何地方发布app(你自己的网站,任何商店,等等),因为Android生态系统是开源的。但是,Google Play是最广为人知的,最受人信任的,最流行的应用商店,而且有谷歌做后盾。Amazon Appstore是kindle 设备的默认可信商店。如果用户想要从不可信来源安装第三方app,他们必须要在设置中显式的开启相关设定。
Android设备上的app可经几种途径安装:使用USB本地安装,从谷歌官方app商店(Google Play Store)或其他应用商店安装。
不同于其他供应商在发布之前先审查app。Google只是简单的用已知的恶意软件签名扫一遍,者就缩短了app的发布到可下载的时间周期。
发布app很简单,最主要的操作就是让已经签名的.apk文件可以下载。在Google Play,发布流程是先创建一个账号,然后通过给定的接口发布app。更多细节请查阅Android官方文档:
https://developer.android.com/distribute/googleplay/start.html.
- End -
看雪ID:BDomne
https://bbs.pediy.com/user-743775.htm
本文由看雪翻译小组 lumou 编译, jasonk龙莲校对
来源:xyphanajay@github
转载请注明来自看雪社区
热门图书推荐:
(点击图片即可进入)
热门技术文章:
公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com